/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;

namespace DaveSexton.DocProject.DocSites
{
	public sealed class UnaryQueryOperation : QueryOperation
	{
		#region Public Properties
		public QueryExpression Operand
		{
			get
			{
				return operand;
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				if (IsOptimized && !value.IsOptimized)
					operand = value.Optimize();
				else
					operand = value;

				if (operand != null)
					operand.Parent = this;
			}
		}

		public override IEnumerable<QueryExpression> Operands
		{
			get
			{
				yield return operand;
			}
		}

		public override int OperandCount
		{
			get
			{
				return (operand == null) ? 0 : 1;
			}
		}

		public override bool MissingOperand
		{
			get
			{
				if (operand == null)
					return true;

				QueryOperation operation = operand as QueryOperation;

				return operation != null && operation.MissingOperand;
			}
		}

		public override bool IsOptimized
		{
			get
			{
				return optimized || !Operator.IsOptimized;
			}
		}
		#endregion

		#region Private / Protected
		private QueryExpression operand;
		private readonly bool optimized;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="UnaryQueryOperation" /> class.
		/// </summary>
		public UnaryQueryOperation(QueryOperator op)
			: base(op)
		{
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="UnaryQueryOperation" /> class.
		/// </summary>
		public UnaryQueryOperation(QueryExpression operand, QueryOperator op)
			: base(op)
		{
			if (operand == null)
				throw new ArgumentNullException("operand");

			this.operand = operand;

			if (operand != null)
				operand.Parent = this;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="UnaryQueryOperation" /> class.
		/// </summary>
		public UnaryQueryOperation(QueryOperator op, QueryExpression parent)
			: base(op, parent)
		{
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="UnaryQueryOperation" /> class.
		/// </summary>
		public UnaryQueryOperation(QueryExpression operand, QueryOperator op, QueryExpression parent)
			: base(op, parent)
		{
			if (operand == null)
				throw new ArgumentNullException("operand");

			this.operand = operand;

			if (operand != null)
				operand.Parent = this;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="UnaryQueryOperation" /> class.
		/// </summary>
		private UnaryQueryOperation(QueryExpression operand, QueryOperator op, bool optimized)
			: base(op)
		{
			this.operand = operand;
			this.optimized = optimized;

			if (operand != null)
				operand.Parent = this;
		}
		#endregion

		#region Methods
		public override QueryExpression Normalize()
		{
			if (operand == null || operand is EmptyQueryExpression)
				return EmptyQueryExpression.Value;
			else
			{
				QueryExpression normalized = operand.Normalize();

				if (normalized == null || normalized is EmptyQueryExpression)
					return EmptyQueryExpression.Value;
				else
					return new UnaryQueryOperation(normalized, Operator, IsOptimized);
			}
		}

		public override QueryExpression Optimize()
		{
			if (operand == null || operand is EmptyQueryExpression)
				return EmptyQueryExpression.Value;
			else
			{
				// NOTE: call Optimize() on the operand regardless of whether IsOptimized is already true
				// so that at least a new instance is returned.
				QueryExpression operandOptimized = operand.Optimize();

				if (operandOptimized == null || operandOptimized is EmptyQueryExpression)
					return EmptyQueryExpression.Value;
				else
					return new UnaryQueryOperation(operandOptimized, Operator, true);
			}
		}

		public override void AppendOperand(QueryExpression nextOperand)
		{
			if (this.operand == null)
			{
				this.operand = nextOperand;

				if (nextOperand != null)
					nextOperand.Parent = this;
			}
			else
			{
				QueryOperation operation = this.operand as QueryOperation;

				if (operation != null && operation.MissingOperand)
					operation.AppendOperand(nextOperand);
				else
					throw new InvalidOperationException(Resources.Errors.NoMissingOperands);
			}
		}

		public override void Evaluate(Action<QueryExpression> action)
		{
			if (operand != null)
			{
				operand.Evaluate(action);

				action(this);
			}
		}

		public override void AppendToString(StringBuilder output)
		{
			if (output == null)
				throw new ArgumentNullException("output");

			output.Append(Operator);

			if (operand != null)
				operand.AppendToString(output);
		}
		#endregion
	}
}
